﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.Timers;
using System.Diagnostics;
using System.Security.Cryptography;

namespace CurlingServer
{
    public class CurlingService : ICurlingService
    {
        //private static List<string> AuthTokens;
        private static Dictionary<string, PlayerProfile> Players;
        private static List<GameRequest> GameRequests;
        private static List<Opponents> Opponents;
        private static Dictionary<string, DateTime> LastPlayerUpdate;
        private static DateTime LastPlayersChangedTime;

        private static string LastPlayerOnlineID;
        private static DateTime LastPlayerOnlineTime;
        private static DateTime LastPlayerOnlineTime_;
        private static Timer timer;
        private Random rnd;

        private static Dictionary<string, Queue<string>> GameData;

        private CurlingService()
        {
            if (Players == null || LastPlayerUpdate == null || GameRequests == null || Opponents == null || GameData == null)
            {
                Players = new Dictionary<string, PlayerProfile>();

                LastPlayerUpdate = new Dictionary<string, DateTime>();

                GameRequests = new List<GameRequest>();

                Opponents = new List<Opponents>();

                GameData = new Dictionary<string, Queue<string>>();
            } 

            if (timer == null)
            {
                timer = new Timer(1000);
                timer.Elapsed += new ElapsedEventHandler(Timer_CheckOnline);
            }

            if (rnd == null)
            {
                rnd = new Random();
            }
        }

        void Timer_CheckOnline(object sender, ElapsedEventArgs e)
        {
            //lock (GameRequests)
            //{
                foreach (GameRequest gameRequest in GameRequests)
                {
                    if (DateTime.Now.Subtract(gameRequest.TimeStamp) > TimeSpan.FromSeconds(10))
                    {
                        //GameRequests.Remove(gameRequest);
                        gameRequest.IsAccepted = false;

                        break;
                    }
                }

                //for (int i = 0; i < GameRequests.Count - 1; i++)
                //{
                //    for (int j = i + 1; j < GameRequests.Count; j++)
                //    {
                //        if (GameRequests[i].Player1.ID == GameRequests[j].Player2.ID && GameRequests[i].Player2.ID == GameRequests[j].Player1.ID)
                //        {
                //            GameRequests[i].IsOpponentNotificated = true;
                //            GameRequests[i].IsAccepted = true;
                //            GameRequests[j].IsOpponentNotificated = true;
                //            GameRequests[j].IsAccepted = true;
                //        }
                //    }
                //}
            //}

            lock (Players) lock (LastPlayerUpdate)
            {
                foreach (string AuthToken in LastPlayerUpdate.Keys)
                {
                    if (DateTime.Now.Subtract(LastPlayerUpdate[AuthToken]) > TimeSpan.FromSeconds(10))
                    {
                        if (LastPlayerOnlineID != Players[AuthToken].ID)
                        {
                            LastPlayerOnlineID = Players[AuthToken].ID;

                            LastPlayerOnlineTime_ = LastPlayerOnlineTime;
                            LastPlayerOnlineTime = DateTime.Now;
                        }
                        else
                        {
                            LastPlayerOnlineTime = DateTime.Now;
                        }

                        RemoveUser(AuthToken);

                        break;
                    }
                }

                if (Players.Count == 0)
                    timer.Stop();
            }
        }

        public string SetStatusOnline(PlayerProfile playerProfile, string ClientVersion)
        {
            lock (Players) lock (LastPlayerUpdate)
            {
                if (!timer.Enabled)
                    timer.Start();

                playerProfile.ID = CalculateMD5Hash(playerProfile.ID);
                
                foreach (string AuthToken_ in Players.Keys)
                {
                    if (Players[AuthToken_].ID == playerProfile.ID)
                    {
                        RemoveUser(AuthToken_);
                        //Players.Remove(AuthToken_);
                        //LastPlayerUpdate.Remove(AuthToken_);
                        break;
                    }
                }

                string AuthToken = Guid.NewGuid().ToString();
                //AuthTokens.Add(AuthToken);
                AddUser(AuthToken, playerProfile);
                //Players.Add(AuthToken, playerProfile);

                //LastPlayerUpdate.Add(AuthToken, DateTime.Now);
                return AuthToken;
            }
        }

        public void SetStatusOffline(string AuthToken)
        {
            lock (Players) lock (LastPlayerUpdate)
            {
                if (Players.ContainsKey(AuthToken))
                {
                    PlayerProfile playerProfile = Players[AuthToken];

                    if (LastPlayerOnlineID != Players[AuthToken].ID)
                    {
                        LastPlayerOnlineID = Players[AuthToken].ID;

                        LastPlayerOnlineTime_ = LastPlayerOnlineTime;
                        LastPlayerOnlineTime = DateTime.Now;
                    }
                    else
                    {
                        LastPlayerOnlineTime = DateTime.Now;
                    }

                    RemoveUser(AuthToken);
                    //Players.Remove(AuthToken);
                    //LastPlayerUpdate.Remove(AuthToken);
                }

                if (Players.Count == 0)
                    timer.Stop();
            }
        }
        
        public List<PlayerProfile> GetUsersOnline(string AuthToken, bool EmptyIfNotChanged)
        {
            if (!LastPlayerUpdate.ContainsKey(AuthToken) || !Players.ContainsKey(AuthToken))
            {
                return null;
            }

            lock (LastPlayerUpdate)
            {
                if (!EmptyIfNotChanged || LastPlayerUpdate[AuthToken] < LastPlayersChangedTime)
                {
                    LastPlayerUpdate[AuthToken] = DateTime.Now;
                    List<PlayerProfile> players = Players.Values.ToList();
                    players.Remove(Players[AuthToken]);
                    for (int i = players.Count - 1; i >= 0; i--)
                    {
                        if (players[i].State != PlayerState.Online)
                            players.RemoveAt(i);
                    }
                    return players;
                }
                else
                {
                    LastPlayerUpdate[AuthToken] = DateTime.Now;

                    List<PlayerProfile> NullList = new List<PlayerProfile>();
                    NullList.Add(null);

                    return NullList;
                }
            }
        }

        public TimeSpan? GetLastVisitorInterval(string AuthToken)
        {
            if (!Players.ContainsKey(AuthToken))
            {
                return null;
            }

            if (Players[AuthToken].ID == LastPlayerOnlineID)
            {
                if (LastPlayerOnlineTime_ != new DateTime(0))
                {
                    return DateTime.Now.Subtract(LastPlayerOnlineTime_);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                if (LastPlayerOnlineTime != new DateTime(0))
                {
                    return DateTime.Now.Subtract(LastPlayerOnlineTime);
                }
                else
                {
                    return null;
                }
            }
        }

        public int GetAlreadyPlayingCount(string AuthToken)
        {
            return Opponents.Count * 2;
        }

        public void ChooseOpponent(string AuthToken, string OpponentID)
        {
            foreach (GameRequest gameRequest in GameRequests)
            {
                if (/*gameRequest.Player1.ID.Equals(Opponent.ID) ||*/ gameRequest.Player2.ID.Equals(OpponentID))
                {
                    return;
                }
            }

            PlayerProfile Opponent = null;

            foreach (PlayerProfile Opponent_ in Players.Values)
            {
                if (Opponent_.ID.Equals(OpponentID))
                {
                    Opponent = Opponent_;
                    break;
                }
            }

            if (Opponent == null)
                return;

            lock (GameRequests)
            {
                GameRequests.Add(new GameRequest() { Player1 = Players[AuthToken], Player2 = Opponent, TimeStamp = DateTime.Now });
            }
        }

        public PlayerProfile CheckRequests(string AuthToken)
        {
            lock (GameRequests)
            {
                for (int i = 0; i < GameRequests.Count - 1; i++)
                {
                    for (int j = i + 1; j < GameRequests.Count; j++)
                    {
                        if (GameRequests[i].Player1.ID == GameRequests[j].Player2.ID && GameRequests[i].Player2.ID == GameRequests[j].Player1.ID && !GameRequests[i].IsAccepted.HasValue && !GameRequests[j].IsAccepted.HasValue)
                        {
                            GameRequests[i].IsOpponentNotificated = true;
                            GameRequests[i].IsAccepted = true;
                            GameRequests[j].IsOpponentNotificated = true;
                            GameRequests[j].IsAccepted = true;

                            BeginPlay(GameRequests[i].Player1, GameRequests[i].Player2);
                        }
                    }
                }

                foreach (GameRequest gameRequest in GameRequests)
                {
                    if (Players.ContainsKey(AuthToken))
                    {
                        if (gameRequest.Player2.ID.Equals(Players[AuthToken].ID) && !gameRequest.IsOpponentNotificated)
                        {
                            gameRequest.IsOpponentNotificated = true;

                            return gameRequest.Player1;
                        }
                    }
                }
            }

            return null;
        }

        public void ProcessRequest(string AuthToken, string OpponentID, bool Accept)
        {
            if (Players.ContainsKey(AuthToken))
            {
                lock (GameRequests)
                {
                    foreach (GameRequest gameRequest in GameRequests)
                    {
                        if (gameRequest.Player2.ID.Equals(Players[AuthToken].ID) && gameRequest.Player1.ID.Equals(OpponentID))
                        {
                            //GameRequests.Remove(gameRequest);

                            gameRequest.IsAccepted = Accept;

                            if (Accept)
                            {
                                //Opponents.Add(new Opponents() { Player1 = Players[AuthToken], Player2 = gameRequest.Player2 });
                                BeginPlay(gameRequest.Player1, gameRequest.Player2);
                            }

                            return;
                        }
                    }
                }
            }
        }

        public bool? CheckRequestResult(string AuthToken, string OpponentID)
        {
            lock (GameRequests)
            {
                foreach (GameRequest gameRequest in GameRequests)
                {
                    if (gameRequest.Player1.ID.Equals(Players[AuthToken].ID) && gameRequest.Player2.ID.Equals(OpponentID))
                    {
                        if (gameRequest.IsAccepted.HasValue)
                        {
                            GameRequests.Remove(gameRequest);
                        }

                        return gameRequest.IsAccepted;
                    }
                }
            }

            return null;
        }

        public bool? IsIamFirstPlayer(string AuthToken)
        {
            if (!Players.ContainsKey(AuthToken))
                return null;

            foreach (Opponents Opponent in Opponents)
            {
                if (Players[AuthToken].ID.Equals(Opponent.Player1.ID))
                {
                    return true;
                }
                else if (Players[AuthToken].ID.Equals(Opponent.Player2.ID))
                {
                    return false;
                }
            }
            
            return null;
        }

        //public string GetOpponentStreamID(string AuthToken)
        //{
        //    if (!Players.ContainsKey(AuthToken))
        //        return null;


        //}

        private void BeginPlay(PlayerProfile Player1, PlayerProfile Player2)
        {
            bool Reverse = false;// (rnd.Next(0, 2) == 0) ? true : false;

            for (int i = Opponents.Count - 1; i >= 0; i--)
            {
                if (Player1.ID.Equals(Opponents[i].Player1.ID) || Player1.ID.Equals(Opponents[i].Player2.ID) || Player2.ID.Equals(Opponents[i].Player1.ID) || Player2.ID.Equals(Opponents[i].Player2.ID))
                {
                    Opponents.RemoveAt(i);
                }
            }

            Player1.State = PlayerState.InGame;
            Player2.State = PlayerState.InGame;

            LastPlayersChangedTime = DateTime.Now;

            Opponents.Add(new Opponents() { Player1 = (Reverse) ? Player2 : Player1, Player2 = (Reverse) ? Player1 : Player2 });
        }

        public void UploadData(string AuthToken, string OpponentID, string GameDataXml)
        {
            if (!Players.ContainsKey(AuthToken) || !GameData.ContainsKey(OpponentID))
                return;

            LastPlayerUpdate[AuthToken] = DateTime.Now;

            lock (GameData)
            {
                if (GameDataXml != null)
                {
                    Debug.WriteLine(DateTime.Now.ToString() + " Upload");
                    GameData[OpponentID].Enqueue(GameDataXml);
                }
            }
        }

        public string DownloadData(string AuthToken)
        {
            if (!Players.ContainsKey(AuthToken) || Players[AuthToken].State == PlayerState.Offline)
                return null;

            LastPlayerUpdate[AuthToken] = DateTime.Now;

            lock (GameData)
            {
                if (GameData[Players[AuthToken].ID].Count > 0)
                {
                    Debug.WriteLine(DateTime.Now.ToString() + " Download");
                    return GameData[Players[AuthToken].ID].Dequeue();
                }
                else
                {
                    return "";
                }
            }
        }

        public string CalculateMD5Hash(string input)
        {
            // step 1, calculate MD5 hash from input
            MD5 md5 = System.Security.Cryptography.MD5.Create();
            byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
            byte[] hash = md5.ComputeHash(inputBytes);

            // step 2, convert byte array to hex string
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < hash.Length; i++)
            {
                sb.Append(hash[i].ToString("X2"));
            }
            return sb.ToString();
        }

        private void AddUser(string AuthToken, PlayerProfile playerProfile)
        {
            playerProfile.State = PlayerState.Online;
            Players.Add(AuthToken, playerProfile);
            LastPlayerUpdate.Add(AuthToken, DateTime.Now);

            if (GameData.ContainsKey(playerProfile.ID))
                GameData.Remove(playerProfile.ID);

            GameData.Add(playerProfile.ID, new Queue<string>());

            LastPlayersChangedTime = DateTime.Now;
        }

        private void RemoveUser(string AuthToken)
        {
            if (Players.ContainsKey(AuthToken))
            {
                foreach (Opponents Opponent in Opponents)
                {
                    if (Opponent.Player1.ID.Equals(Players[AuthToken].ID) || Opponent.Player2.ID.Equals(Players[AuthToken].ID))
                    {
                        Opponent.Player1.State = PlayerState.Offline;
                        Opponent.Player2.State = PlayerState.Offline;

                        Opponents.Remove(Opponent);

                        break;
                    }
                }

                for (int i = GameRequests.Count - 1; i >= 0; i--)
                {
                    if (GameRequests[i].Player1.ID.Equals(Players[AuthToken].ID) || GameRequests[i].Player2.ID.Equals(Players[AuthToken].ID))
                        GameRequests.RemoveAt(i);
                }

                LastPlayerUpdate.Remove(AuthToken);
                Players.Remove(AuthToken);

                GameData.Remove(AuthToken);

                LastPlayersChangedTime = DateTime.Now;
            }
        }
    }
}
